package org.wukongcrm.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.access.AuthorizationServiceException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;
import org.wukongcrm.domain.entity.AdminUser;
import org.wukongcrm.domain.entity.Menu;
import org.wukongcrm.domain.model.LoginUser;
import org.wukongcrm.domain.model.R;
import org.wukongcrm.filter.TokenFilter;
import org.wukongcrm.mapper.AdminUserMapper;
import org.wukongcrm.mapper.MenuMapper;
import org.wukongcrm.utils.JwtUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 安全配置类
 *
 * @author ZhongWang
 * @since 2025 02 21:15:07
 */
@Configuration
@AllArgsConstructor
public class SecurityConfig {

    public static final Integer USER_MANAGER_ID = 1;

    private final AdminUserMapper userMapper;

    private final MenuMapper permissionsMapper;

    private final ObjectMapper objectMapper;

    private final StringRedisTemplate stringRedisTemplate;

    private final TokenFilter tokenFilter;

    /**
     * 密码加密器
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder () {
        return new BCryptPasswordEncoder();
    }

    /**
     * security中，定义用户有两种方式，第一种内存式，第二种数据库方式
     * 今天使用简单的内存式
     * <p>
     * 在security中分为两个部分：
     * 1）认证：登录用户
     * 2）授权：权限信息
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService () {
        return username -> {
            //username:这是前台登录的时候传入的用户名称
            //根据传入的用户名称从数据库进行查询看是否存在这个用户
            QueryWrapper<AdminUser> userQuery = new QueryWrapper<>();
            userQuery.lambda().eq(AdminUser::getUsername, username);
            //user的id信息
            AdminUser user = userMapper.selectOne(userQuery);
            if (user == null) {
                throw new UsernameNotFoundException("用户不存在，请换一个!");
            }
            List<Menu> permissions = null;
            //根据用户获取到用户的权限信息
            if (user.getUserNo() == USER_MANAGER_ID.intValue()) {
                //超级管理员，使用默认方法查询所有权限信息
                permissions = permissionsMapper.selectList(null);
            } else {
                //非超级管理员的权限信息
                permissions = permissionsMapper.findPermissionsByUserId(user.getUserNo());
            }
            if (permissions == null) {
                throw new AuthorizationServiceException("该用户权限信息为空，不能登录!");
            }
            String encodePassword = passwordEncoder().encode(user.getPassword());
            user.setPassword(encodePassword);
            return new LoginUser(user, permissions);
        };
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http)
            throws Exception {
        //注意：如果需要自定义登录，先要禁止使用csrf(防伪链)，也可以不禁用，那么你就需要
        //在每一个页面都加上页面标识
        //比如：<input type="hidden" name="_csrf" th:value="${_csrf.token}">]
        http.csrf().disable();
        //设置登录界面信息
        http.formLogin()
                // get
                .loginPage("/login")
                .usernameParameter("username")
                .passwordParameter("password")
                // post
                .loginProcessingUrl("/login")
                .successHandler(((request, response, authentication) -> {
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json;charset=UTF-8");

                    // 获取到登录用户
                    LoginUser loginUser = (LoginUser) authentication.getPrincipal();

                    // 获取具体用户信息
                    AdminUser users = loginUser.getUsers();

                    // 根据用户信息生成token
                    String token = JwtUtils.generateToken(users.getUserNo(), users.getUsername());

                    // 将前端所需数据封装成map
                    Map<String, String> userInfo = new HashMap<>();
                    userInfo.put("username", users.getUsername());
                    userInfo.put("token", token);

                    // 将用户信息存入redis
                    String jsonLoginUser = objectMapper.writeValueAsString(loginUser);
                    stringRedisTemplate.opsForValue().set("permission:" + users.getUsername(), jsonLoginUser);

                    R<Object> success = R.success(200, "登录成功", userInfo);
                    String successMsg = objectMapper.writeValueAsString(success);

                    // 将结果返回前端
                    response.getWriter().write(successMsg);
                })).failureHandler((request, response, exception) -> {
                    postMsg(401, response, "登录失败，用户名或密码错误", exception.getMessage());
                })
                .permitAll();
        // 释放登录地址
        http.authorizeRequests().mvcMatchers("/login").permitAll();
        List<Menu> permissions = permissionsMapper.selectList(null);
        for (Menu permission : permissions) {
            if(StringUtils.hasLength(permission.getPerm()) && StringUtils.hasLength(permission.getPermUrl())) {
                http.authorizeRequests()
                        .mvcMatchers(permission.getPermUrl()).hasAuthority(permission.getPerm());
            }
        }
        // 权限不足提示消息
        http.exceptionHandling().accessDeniedHandler(
                (request, response, accessDeniedException) ->
                        postMsg(403, response, "权限不足", accessDeniedException.getMessage()));
        // 未登录提示消息
        http.exceptionHandling().authenticationEntryPoint(
                (request, response, authException) ->
                        postMsg(401, response, "请先登录", authException.getMessage()));
        // 禁用在session环境下创建session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //所有地址都需要登录才能访问
        http.authorizeRequests().antMatchers("/**").authenticated();
        //.permitAll() ：释放此请求地址
        // 替换spring security默认的UsernamePasswordAuthenticationFilter
        http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    /**
     * 返回json格式的提示信息
     *
     * @param code 状态码
     * @param response 响应对象
     * @param msg 提示信息
     * @param errorMsg 错误信息
     * @throws IOException  异常
     */
    private void postMsg (Integer code, HttpServletResponse response, String msg, String errorMsg) throws IOException {
        R<String> fail = R.error(code, msg, errorMsg);
        String failMsg = objectMapper.writeValueAsString(fail);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(failMsg);
    }

}
